﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Markup;
using System.Windows;
using System.Windows.Media;
using SDF.Primitives;
using SDF.Pagination;

namespace SDF
{
    [ContentProperty("Items")]
    public class List : SmartBlock
    {
        #region Constructor

        public List()
        {
            Items = new ListItemCollection(this);
            Items.ChildAdded += OnItemAdded;
            Items.ChildRemoved += OnItemRemoved;
        }


        #endregion

        #region DependencyProperties

        public static readonly DependencyProperty MarkerStyleProperty =
            System.Windows.Documents.List.MarkerStyleProperty.AddOwner(typeof(List));

        public static readonly DependencyProperty MarkerOffsetProperty =
            System.Windows.Documents.List.MarkerOffsetProperty.AddOwner(typeof(List));

        public static readonly DependencyProperty ItemOffsetProperty = DependencyProperty.Register(
            "ItemOffset",
            typeof(double),
            typeof(List),
            new FrameworkPropertyMetadata(double.NaN));

        #endregion

        #region Properties

        public ListItemCollection Items { get; private set; }

        public TextMarkerStyle MarkerStyle
        {
            get { return (TextMarkerStyle)GetValue(MarkerStyleProperty); }
            set { SetValue(MarkerStyleProperty, value); }
        }

        public double MarkerOffset
        {
            get { return (double)GetValue(MarkerOffsetProperty); }
            set { SetValue(MarkerOffsetProperty, value); }
        }

        public double ItemOffset
        {
            get { return (double)GetValue(ItemOffsetProperty); }
            set { SetValue(ItemOffsetProperty, value); }
        }

        #endregion

        #region Layout Methods

        protected override Size MeasureOverride(Size availableSize)
        {
            Size result = new Size();
            Size constraint = availableSize;
            constraint.Height = double.PositiveInfinity;
            constraint.Width = Math.Max(0, constraint.Width - (ActualPadding.Left + ActualPadding.Right));

            double previousBottomMargin = 0;

            for(int i = 0; i < this.Items.Count; i++)
            {
                var block = Items[i];
                
                block.Measure(constraint);
                Size desiredSize = block.DesiredSize;

                // Width is easy. Just get the largest child width
                result.Width = Math.Max(result.Width, desiredSize.Width);

                // Height is more complicated.
                // Desired size returned by each child includes the child's margin, but:
                // 
                // 1. We don't want top margin on the first element and bottom margin on the last one, **IF** those margins
                //    are set to Auto
                //
                // 2. The space between two adjacent elements will be the largest of the bottom margin of the previous or the
                //    top margin of the following element.
                double topMargin = Math.Max(previousBottomMargin, block.ActualMargin.Top);
                if ((i == 0) && (double.IsNaN(block.Margin.Top)))
                    topMargin = 0;
                double bottomMargin = block.ActualMargin.Bottom;
                if ((i == this.Items.Count - 1) && (double.IsNaN(block.Margin.Bottom)))
                    bottomMargin = 0;

                double elementActualHeight = Math.Max(0, desiredSize.Height - (block.ActualMargin.Top + block.ActualMargin.Bottom));

                result.Height += (topMargin - previousBottomMargin) + elementActualHeight + bottomMargin;

                previousBottomMargin = block.ActualMargin.Bottom;
            }

            // Add padding to the resulting size
            result.Height = Math.Max(0, result.Height + (ActualPadding.Top + ActualPadding.Bottom));
            result.Width = Math.Max(0, result.Width + (ActualPadding.Left + ActualPadding.Right));
            return result;
        }

        protected override Size ArrangeOverride(Size availableSize)
        {
            Rect rect = new Rect();
            double previousBottomMargin = 0;
            
            double availableWidth = Math.Max(0, availableSize.Width - (ActualPadding.Left + ActualPadding.Right));
            double finalHeight = 0;
            rect.X = ActualPadding.Left;
            rect.Y = ActualPadding.Top;

            for (int i = 0; i < this.Items.Count; i++)
            {
                var block = Items[i];

                rect.Height = block.DesiredSize.Height;
                rect.Width = Math.Max(availableWidth, block.DesiredSize.Width);

                double topMargin = Math.Max(previousBottomMargin, block.ActualMargin.Top);
                if ((i == 0) && (double.IsNaN(block.Margin.Top)))
                    topMargin = 0;
                rect.Y = rect.Y + (topMargin - previousBottomMargin)- block.ActualMargin.Top;
                block.Arrange(rect);
                previousBottomMargin = block.ActualMargin.Bottom;
                finalHeight = rect.Bottom;
                double bottomMargin = block.ActualMargin.Bottom;
                if ((i == this.Items.Count - 1) && (double.IsNaN(block.Margin.Bottom)))
                    bottomMargin = 0;
                finalHeight -= (block.ActualMargin.Bottom - bottomMargin);
                rect.Y = finalHeight;
            }

            return new Size(availableSize.Width, finalHeight);
        }

        #endregion

        #region Visual Children Implementation

        protected override int VisualChildrenCount
        {
            get
            {
                int result = Items.Count;
                return result;
            }
        }

        protected override Visual GetVisualChild(int index)
        {
            if (index < 0 || index >= Items.Count)
                throw new ArgumentOutOfRangeException("index");

            Visual result = Items[index];
            return result;
        }

        private void OnItemAdded(object sender, SmartDataObjectCollectionEventArgs<ListItem> e)
        {
            AddVisualChild(e.Item);
        }

        private void OnItemRemoved(object sender, SmartDataObjectCollectionEventArgs<ListItem> e)
        {
            RemoveVisualChild(e.Item);
        }

        #endregion

        #region Harvesting

        public override HarvestResult Harvest(HarvestingContext context, double remainingHeight, SmartBlock previousBlock)
        {
            // If we are at the top level and we follow another element,
            // we may need to break the page or column
            if (PageContent.IsDirectChild(this) && (previousBlock != null))
            {
                if (this.BreakPageBefore)
                    return HarvestResult.BreakPage;

                if (this.BreakColumnBefore)
                    return HarvestResult.BreakColumn;
            }

            bool isPartiallyHarvested = IsPartiallyHarvested(context);

            // If this is the first part, count the margin
            var harvestedMargin = this.ActualMargin;
            if (isPartiallyHarvested)
                harvestedMargin.Top = 0;
            else if ((previousBlock == null) && double.IsNaN(this.Margin.Top))
                harvestedMargin.Top = 0;
            remainingHeight -= harvestedMargin.Top;


            List harvestedElement = null;
            ListItem previousChild = null;
            bool addColumnBreak = false;
            bool addPageBreak = false;
            double previousBottomMargin = 0;
            bool isFullyHarvested = false;

            foreach (var child in this.Items)
            {
                if (context.IsFullyHarvested(child))
                    continue;

                // Two adjacent margins will overlap, so we need to adjust remaining width accordingly
                remainingHeight += Math.Min(previousBottomMargin, child.ActualMargin.Top);

                var childResult = child.Harvest(context, remainingHeight, previousChild);
                remainingHeight = childResult.RemainingHeight;
                previousBottomMargin = child.ActualMargin.Bottom;

                // Let's see if the child fits
                if (childResult.HarvestedBlock == null)
                {
                    addColumnBreak = childResult.AddColumnBreak;
                    addPageBreak = childResult.AddPageBreak;
                    break;
                }
                else
                {
                    if (harvestedElement == null)
                    {
                        harvestedElement = this.GetEmptyCopy() as List;
                    }

                    // Add child to harvested element
                    harvestedElement.Items.Add(childResult.HarvestedBlock as ListItem);
                }

                previousChild = child;
                if (this.Items.IndexOf(child) == Items.Count - 1)
                {
                    // Last child. We are finished, if it is finished
                    if (context.IsFullyHarvested(child))
                        isFullyHarvested = true;
                }

                if (childResult.AddColumnBreak || childResult.AddPageBreak)
                {
                    addColumnBreak = childResult.AddColumnBreak;
                    addPageBreak = childResult.AddPageBreak;
                    break;
                }
            }

            if (isFullyHarvested)
                context.MarkAsFullyHarvested(this);
            else
                harvestedMargin.Bottom = 0; // We are going to split, so remove the bottom margin
            
            harvestedElement.Margin = harvestedMargin;
            remainingHeight -= harvestedMargin.Bottom;
            
            return new HarvestResult(harvestedElement, remainingHeight, addColumnBreak, addPageBreak);
        }

        public override SmartBlock GetEmptyCopy()
        {
            var result = new List();
            CopyProperties(result);
            result.MarkerStyle = this.MarkerStyle;
            result.MarkerOffset = this.MarkerOffset;
            result.ItemOffset = this.ItemOffset;
            return result;
        }

        #endregion

        #region CalculateMinHeight

        public override double CalculateMinHeight()
        {
            if (Items.Count > 0)
            {
                var firstItem = Items[0];
                if (firstItem != null)
                    return firstItem.CalculateMinHeight();
            }

            return 0;
        }

        #endregion

    }
}
